# SEE modeldata package for new datasets
library(tidyverse) # for graphing and data cleaning
## Warning: package 'tibble' was built under R version 4.0.4
library(tidymodels) # for modeling
## Warning: package 'tidymodels' was built under R version 4.0.4
## Warning: package 'dials' was built under R version 4.0.4
## Warning: package 'infer' was built under R version 4.0.4
## Warning: package 'modeldata' was built under R version 4.0.4
## Warning: package 'parsnip' was built under R version 4.0.4
## Warning: package 'recipes' was built under R version 4.0.4
## Warning: package 'rsample' was built under R version 4.0.4
## Warning: package 'tune' was built under R version 4.0.4
## Warning: package 'workflows' was built under R version 4.0.4
## Warning: package 'yardstick' was built under R version 4.0.4
library(stacks) # for stacking models
## Warning: package 'stacks' was built under R version 4.0.4
library(naniar) # for examining missing values (NAs)
## Warning: package 'naniar' was built under R version 4.0.4
library(lubridate) # for date manipulation
library(moderndive) # for King County housing data
## Warning: package 'moderndive' was built under R version 4.0.4
library(DALEX) # for model interpretation
## Warning: package 'DALEX' was built under R version 4.0.4
library(DALEXtra) # for extension of DALEX
## Warning: package 'DALEXtra' was built under R version 4.0.4
library(patchwork) # for combining plots nicely
library(dbplyr) # for SQL query "cheating" - part of tidyverse but needs to be loaded separately
library(mdsr) # for accessing some databases - goes with Modern Data Science with R textbook
## Warning: package 'mdsr' was built under R version 4.0.4
library(RMySQL) # for accessing MySQL databases
## Warning: package 'RMySQL' was built under R version 4.0.4
library(RSQLite) # for accessing SQLite databases
## Warning: package 'RSQLite' was built under R version 4.0.4
#mapping
library(maps) # for built-in maps
library(sf) # for making maps using geom_sf
library(ggthemes) # Lisa added - I like theme_map() for maps :)
#tidytext
library(tidytext) # for text analysis, the tidy way!
## Warning: package 'tidytext' was built under R version 4.0.4
library(textdata)
## Warning: package 'textdata' was built under R version 4.0.4
library(reshape2)
## Warning: package 'reshape2' was built under R version 4.0.4
library(wordcloud) # for wordcloud
## Warning: package 'wordcloud' was built under R version 4.0.4
library(stopwords)
## Warning: package 'stopwords' was built under R version 4.0.4
theme_set(theme_minimal()) # Lisa's favorite theme
library(lime)
## Warning: package 'lime' was built under R version 4.0.4
When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.
Put it on GitHub!
From now on, GitHub should be part of your routine when doing assignments. I recommend making it part of your process anytime you are working in R, but I’ll make you show it’s part of your process for assignments.
Task: When you are finished with the assignment, post a link below to the GitHub repo for the assignment. If you want to post it to your personal website, that’s ok (not required). Make sure the link goes to a spot in the repo where I can easily find this assignment. For example, if you have a website with a blog and post the assignment as a blog post, link to the post’s folder in the repo. As an example, I’ve linked to my GitHub stacking material here.
https://github.com/Niketh27/Advanced_DS_3
Local Interpretable Machine Learning
You are going to use the King County house data and the same random forest model to predict log_price that I used in the tutorial.
data("house_prices")
# Create log_price and drop price variable
house_prices <- house_prices %>%
mutate(log_price = log(price, base = 10)) %>%
# make all integers numeric ... fixes prediction problem
mutate(across(where(is.integer), as.numeric)) %>%
select(-price)
set.seed(327) #for reproducibility
# Randomly assigns 75% of the data to training.
house_split <- initial_split(house_prices,
prop = .75)
house_training <- training(house_split)
house_testing <- testing(house_split)
ranger_recipe <-
recipe(formula = log_price ~ .,
data = house_training) %>%
step_date(date,
features = "month") %>%
# Make these evaluative variables, not included in modeling
update_role(all_of(c("id",
"date")),
new_role = "evaluative")
#define model
ranger_spec <-
rand_forest(mtry = 6,
min_n = 10,
trees = 200) %>%
set_mode("regression") %>%
set_engine("ranger")
#create workflow
ranger_workflow <-
workflow() %>%
add_recipe(ranger_recipe) %>%
add_model(ranger_spec)
#fit the model
set.seed(712) # for reproducibility - random sampling in random forest choosing number of variables
ranger_fit <- ranger_workflow %>%
fit(house_training)
Tasks:
- Choose 3 new observations and do the following for each observation:
- Construct a break-down plot using the default ordering. Interpret the resulting graph. Which variables contribute most to each observation’s prediction?
- Construct a SHAP graph and interpret it. Does it tell a similar story to the break-down plot?
- Construct a LIME graph (follow my code carefully). How close is each original prediction to the prediction from the local model? Interpret the result. You can also try using fewer or more variables in the local model than I used in the example.
rf_explain <-
explain_tidymodels(
model = ranger_fit,
data = house_training %>% select(-log_price),
y = house_training %>% pull(log_price),
label = "rf"
)
## Preparation of a new explainer is initiated
## -> model label : rf
## -> data : 16210 rows 20 cols
## -> data : tibble converted into a data.frame
## -> target variable : 16210 values
## -> predict function : yhat.workflow will be used ( [33m default [39m )
## -> predicted values : No value for predict function target column. ( [33m default [39m )
## -> model_info : package tidymodels , ver. 0.1.2 , task regression ( [33m default [39m )
## -> predicted values : numerical, min = 5.081823 , mean = 5.66507 , max = 6.726297
## -> residual function : difference between y and yhat ( [33m default [39m )
## -> residuals : numerical, min = -0.3147814 , mean = 0.0004295329 , max = 0.2270978
## [32m A new explainer has been created! [39m
house_testing
new_obs1 <- house_testing %>% slice(3333)
new_obs2 <- house_testing %>% slice(1234)
new_obs3 <- house_testing %>% slice(4321)
# Pulls together the data needed for the break-down plot
pp_rf1 <- predict_parts(explainer = rf_explain,
new_observation = new_obs1,
type = "break_down")
pp_rf2 <- predict_parts(explainer = rf_explain,
new_observation = new_obs2,
type = "break_down")
pp_rf3 <- predict_parts(explainer = rf_explain,
new_observation = new_obs3,
type = "break_down")
# Break-down plot
plot(pp_rf1)

plot(pp_rf2)

plot(pp_rf3)

#TODO: Interpret
latitude appears to be the most dominant predictor with all predictions. sqft_living, sqft_above and grade also seem to be important predictors.
rf_shap1 <-predict_parts(explainer = rf_explain,
new_observation = new_obs1,
type = "shap",
B = 10 #number of reorderings - start small
)
rf_shap2 <-predict_parts(explainer = rf_explain,
new_observation = new_obs2,
type = "shap",
B = 10 #number of reorderings - start small
)
rf_shap3 <-predict_parts(explainer = rf_explain,
new_observation = new_obs3,
type = "shap",
B = 10 #number of reorderings - start small
)
plot(rf_shap1)

plot(rf_shap2)

plot(rf_shap3)
Yes, the predictors which are shown to be signifcant are similar in the breakdown plot and the SHAP plot.
set.seed(2)
# NEED these two lines of code always!
# They make sure our explainer is defined correctly to use in the next step
model_type.dalex_explainer <- DALEXtra::model_type.dalex_explainer
predict_model.dalex_explainer <- DALEXtra::predict_model.dalex_explainer
lime_rf1 <- predict_surrogate(explainer = rf_explain,
new_observation = new_obs1 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
## Warning: view does not contain enough variance to use quantile binning. Using
## standard binning instead.
## Warning: yr_renovated does not contain enough variance to use quantile binning.
## Using standard binning instead.
## Warning in gower_work(x = x, y = y, pair_x = pair_x, pair_y = pair_y, n =
## NULL, : skipping variable with zero or non-finite range.
lime_rf2 <- predict_surrogate(explainer = rf_explain,
new_observation = new_obs2 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
## Warning: view does not contain enough variance to use quantile binning. Using
## standard binning instead.
## Warning: yr_renovated does not contain enough variance to use quantile binning.
## Using standard binning instead.
## Warning in gower_work(x = x, y = y, pair_x = pair_x, pair_y = pair_y, n =
## NULL, : skipping variable with zero or non-finite range.
lime_rf3 <- predict_surrogate(explainer = rf_explain,
new_observation = new_obs3 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
## Warning: view does not contain enough variance to use quantile binning. Using
## standard binning instead.
## Warning: yr_renovated does not contain enough variance to use quantile binning.
## Using standard binning instead.
## Warning in gower_work(x = x, y = y, pair_x = pair_x, pair_y = pair_y, n =
## NULL, : skipping variable with zero or non-finite range.
plot(lime_rf1) +
labs(x = "Variable")

plot(lime_rf2) +
labs(x = "Variable")

plot(lime_rf3) +
labs(x = "Variable")

- Describe how you would use the interpretable machine learning tools we’ve learned (both local and global) in future machine learning projects? How does each of them help you?
These tools help us identify the most important predictors in the model and the nature of their relationship with the response variable which helps us intuitively understand how the predictions are being made which helps us make better model choices.
#Writing:TODO
SQL
You will use the airlines data from the SQL database that I used in the example in the tutorial. Be sure to include the chunk to connect to the database here. And, when you are finished, disconnect. You may need to reconnect throughout as it times out after a while.
con_air <- dbConnect(RMySQL::MySQL(),
dbname = "airlines",
host = "mdsr.cdc7tgkkqd0n.us-east-1.rds.amazonaws.com",
user = "mdsr_public",
password = "ImhsmflMDSwR")
dbListTables(con_air)
## [1] "airports" "carriers" "flights" "planes"
Tasks: 1. Create a SQL chunk and an equivalent R code chunk that does the following: for 2017 , for each airport (with its name, not code), and month find the total number of departing flights, the average distance of the flight, and the proportion of flights that arrived more than 20 minutes late. In the R code chunk, write this out to a dataset. (HINT: 1. start small! 2. you may want to do the R part first and use it to “cheat” into the SQL code).(New)
With the dataset you wrote out, create a graph that helps illustrate the “worst” airports in terms of late arrivals. You have some freedom in how you define worst and you may want to consider some of the other variables you computed. Do some theming to make your graph look glamorous (those of you who weren’t in my intro data science class this year may want to watch Will Chase’s Glamour of Graphics talk for inspiration).
Although your graph was truly inspirational, you’ve been requested to “boil it down to a few numbers.” Some people just don’t appreciate all that effort you put in. And, you need to use the already summarized data that you already pulled in from SQL. Create a table with 6 or fewer rows and 3 or fewer columns that summarizes which airport is the “worst” in terms of late arrivals. Be careful with your calculations. You may consider using the kable, kableExtra, or gt packages to make your table look truly spectacular.
# for 2017 , for each airport (with its name, not code), and month find the total number of departing flights, the average distance of the flight, and the proportion of flights that arrived more than 20 minutes late.
tbl(con_air, "flights") %>%
filter(year == 2017) %>%
group_by(origin,month) %>%
summarise(dep_flights = n(), avg_distance = mean(air_time), prop_late_over20 = mean(arr_delay > 20)) %>%
inner_join( tbl(con_air,"airports"), by = c("origin"="faa")) %>%
select(origin: name) %>%
show_query()
## Warning in .local(conn, statement, ...): Decimal MySQL column 2 imported as
## numeric
## Warning in .local(conn, statement, ...): Decimal MySQL column 3 imported as
## numeric
## Warning: Missing values are always removed in SQL.
## Use `mean(x, na.rm = TRUE)` to silence this warning
## This warning is displayed only once per session.
## <SQL>
## SELECT `origin`, `month`, `dep_flights`, `avg_distance`, `prop_late_over20`, `name`
## FROM (SELECT `origin`, `month`, `dep_flights`, `avg_distance`, `prop_late_over20`, `name`, `lat`, `lon`, `alt`, `tz`, `dst`, `city`, `country`
## FROM (SELECT `origin`, `month`, COUNT(*) AS `dep_flights`, AVG(`air_time`) AS `avg_distance`, AVG(`arr_delay` > 20.0) AS `prop_late_over20`
## FROM `flights`
## WHERE (`year` = 2017.0)
## GROUP BY `origin`, `month`) `LHS`
## INNER JOIN `airports` AS `RHS`
## ON (`LHS`.`origin` = `RHS`.`faa`)
## ) `q01`
late_airport <- tbl(con_air, "flights") %>%
filter(year == 2017) %>%
group_by(origin,month) %>%
summarise(dep_flights = n(), avg_distance = mean(air_time), prop_late_over20 = mean(arr_delay > 20)) %>%
inner_join( tbl(con_air,"airports"), by = c("origin"="faa")) %>%
select(origin: name)
## Warning in .local(conn, statement, ...): Decimal MySQL column 2 imported as
## numeric
## Warning in .local(conn, statement, ...): Decimal MySQL column 3 imported as
## numeric
SELECT `origin`, `month`, `dep_flights`, `avg_distance`, `prop_late_over20`, `name`
FROM (SELECT `origin`, `month`, `dep_flights`, `avg_distance`, `prop_late_over20`, `name`, `lat`, `lon`, `alt`, `tz`, `dst`, `city`, `country`
FROM (SELECT `origin`, `month`, COUNT(*) AS `dep_flights`, AVG(`air_time`) AS `avg_distance`, AVG(`arr_delay` > 20.0) AS `prop_late_over20`
FROM `flights`
WHERE (`year` = 2017.0)
GROUP BY `origin`, `month`) `LHS`
INNER JOIN `airports` AS `RHS`
ON (`LHS`.`origin` = `RHS`.`faa`)
) `q01`
Displaying records 1 - 10
| ABE |
5 |
160 |
90.5125 |
0.1125 |
Lehigh Valley Intl |
| ABE |
6 |
134 |
90.2985 |
0.1119 |
Lehigh Valley Intl |
| ABE |
7 |
192 |
90.9167 |
0.1354 |
Lehigh Valley Intl |
| ABE |
8 |
180 |
88.2333 |
0.1000 |
Lehigh Valley Intl |
| ABE |
9 |
206 |
91.5825 |
0.0777 |
Lehigh Valley Intl |
| ABE |
10 |
253 |
94.2806 |
0.1344 |
Lehigh Valley Intl |
| ABE |
11 |
220 |
95.8773 |
0.0909 |
Lehigh Valley Intl |
| ABE |
1 |
191 |
96.9581 |
0.1466 |
Lehigh Valley Intl |
| ABE |
12 |
168 |
98.3869 |
0.2083 |
Lehigh Valley Intl |
| ABE |
2 |
128 |
93.2656 |
0.0703 |
Lehigh Valley Intl |
late_airport %>%
group_by(name) %>%
summarise(mean_prop = mean(prop_late_over20)) %>%
arrange(desc(mean_prop)) %>%
head(30) %>%
ggplot(aes(x = mean_prop,
y = (fct_reorder(name,mean_prop)))) +
geom_col(fill = "lightblue") +
scale_x_continuous(expand = c(0,0),
labels = scales::percent) +
labs(x = "", y = "", title = "Airports with largest % of departing flights that were more than 20min late")+
theme_minimal()
## Warning in .local(conn, statement, ...): Decimal MySQL column 1 imported as
## numeric

# - Although your graph was truly inspirational, you've been requested to "boil it down to a few numbers." Some people just don't appreciate all that effort you put in. And, you need to use the already summarized data that you already pulled in from SQL. Create a table with 6 or fewer rows and 3 or fewer columns that summarizes which airport is the "worst" in terms of late arrivals. Be careful with your calculations. You may consider using the `kable`, `kableExtra`, or `gt` packages to make your table look truly spectacular.
library(gt)
gt(late_airport %>%
group_by(name) %>%
summarise(average_late_percentage= mean(prop_late_over20)*100) %>%
arrange(desc(average_late_percentage)) %>%
head(6))
## Warning in .local(conn, statement, ...): Decimal MySQL column 1 imported as
## numeric
| name |
average_late_percentage |
| Sioux Gateway Col Bud Day Fld |
62.9650 |
| East Texas Rgnl |
50.0000 |
| St. Augustine Airport |
36.4340 |
| Southwest Oregon Regional Airport |
35.3525 |
| Nantucket Mem |
34.9200 |
| Martha\\'s Vineyard |
34.1400 |
- Come up with your own interesting question that data in the airlines database can help you answer. Write a SQL query and equivalent R code chunk to extract the data you need and create an elegant graph to help answer the question. Be sure to write down the question so it is clear.
Question: What’s the busiest carrier 2010 - 2017?
carrier_tot_flights <-tbl(con_air, "flights") %>%
group_by(carrier) %>%
summarise(tot_flights = n()) %>%
inner_join( tbl(con_air,"carriers"), by = c("carrier"="carrier"))
tbl(con_air, "flights") %>%
group_by(carrier) %>%
summarise(tot_flights = n()) %>%
inner_join( tbl(con_air,"carriers"), by = c("carrier"="carrier")) %>%
show_query()
## <SQL>
## SELECT `LHS`.`carrier` AS `carrier`, `tot_flights`, `name`
## FROM (SELECT `carrier`, COUNT(*) AS `tot_flights`
## FROM `flights`
## GROUP BY `carrier`) `LHS`
## INNER JOIN `carriers` AS `RHS`
## ON (`LHS`.`carrier` = `RHS`.`carrier`)
SELECT `LHS`.`carrier` AS `carrier`, `tot_flights`, `name`
FROM (SELECT `carrier`, COUNT(*) AS `tot_flights`
FROM `flights`
GROUP BY `carrier`) `LHS`
INNER JOIN `carriers` AS `RHS`
ON (`LHS`.`carrier` = `RHS`.`carrier`)
Displaying records 1 - 10
| 9E |
558065 |
Endeavor Air Inc. |
| AA |
5216777 |
American Airlines Inc. |
| AS |
1278114 |
Alaska Airlines Inc. |
| B6 |
1986105 |
JetBlue Airways |
| CO |
484589 |
Continental Air Lines Inc. |
| DL |
6469415 |
Delta Air Lines Inc. |
| EV |
4225846 |
ExpressJet Airlines Inc. |
| F9 |
696776 |
Frontier Airlines Inc. |
| FL |
969156 |
AirTran Airways Corporation |
| HA |
588749 |
Hawaiian Airlines Inc. |
carrier_tot_flights %>%
ggplot(aes(x = tot_flights, y= fct_reorder(name, tot_flights))) +
geom_col(fill = "olivedrab2")+
labs( x = "", y = "", title = "Carriers with the most flights (2010 -2017)")+
theme_minimal()

Function Friday
If you need to revisit the material, it is posted on the moodle page. I’ve tried to add all the necessary libraries to the top, but I may have missed something.
geom_sf() tasks:
Using the example from class that we presented as a baseline (or your own if you really want to be ambitious), try to add the following components to the map of the contiguous United States:
- Change the color scheme of the map from the default blue (one option could be viridis).
- Add a dot (or any symbol you want) to the centroid of each state.
- Add a layer onto the map with the counties.
- Change the coordinates of the map to zoom in on your favorite state.
Hint: https://www.r-spatial.org/r/2018/10/25/ggplot2-sf-2.html is a useful reference for some of the questions
library(lwgeom)
## Warning: package 'lwgeom' was built under R version 4.0.5
## Linking to liblwgeom 3.0.0beta1 r16016, GEOS 3.9.0, PROJ 7.2.1
## Warning in fun(libname, pkgname): GEOS versions differ: lwgeom has 3.9.0 sf has
## 3.8.0
## Warning in fun(libname, pkgname): PROJ versions differ: lwgeom has 7.2.1 sf has
## 6.3.1
state <- st_as_sf(maps::map("state",plot = FALSE,fill = TRUE))
state <- state %>%
mutate(area = as.numeric(st_area(state)))
state <- cbind(state, st_coordinates(st_centroid(state)))
## Warning in st_centroid.sf(state): st_centroid assumes attributes are constant
## over geometries of x
## Warning in st_centroid.sfc(st_geometry(x), of_largest_polygon =
## of_largest_polygon): st_centroid does not give correct centroids for longitude/
## latitude data
state %>%
filter(ID == "minnesota")
ggplot(data = state) +
geom_sf(aes(fill = area)) +
coord_sf(xlim = c(-126, -65),
ylim = c(24, 50),
expand = FALSE)+
geom_point(aes(x = X, y = Y), size = 1, shape = 4)+
scale_fill_viridis_c(option = "viridis")

county <- st_as_sf(map("county", plot = FALSE, fill = TRUE))
county <- county %>%
mutate(area = as.numeric(st_area(county)))
ggplot(data = state) +
geom_sf(aes(fill = area)) +
geom_sf(data = county, aes(fill = area))+
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE)+
geom_point(aes(x = X, y = Y), size = 1, shape = 4, color = "white")+
scale_fill_viridis_c(option = "viridis")

ggplot(data = state) +
geom_sf(aes(fill = area)) +
geom_sf(data = county, aes(fill = area))+
coord_sf(xlim = c(-96.5, -89.5),
ylim = c(44, 49.5),
expand = FALSE)+
geom_point(aes(x = X, y = Y), size = 2, shape = 4, color = "white")+
scale_fill_viridis_c(option = "viridis")

tidytext tasks:
Now you will try using tidytext on a new dataset about Russian Troll tweets.
Read about the data
These are tweets from Twitter handles that are connected to the Internet Research Agency (IRA), a Russian “troll factory.” The majority of these tweets were posted from 2015-2017, but the datasets encompass tweets from February 2012 to May 2018.
Three of the main categories of troll tweet that we will be focusing on are Left Trolls, Right Trolls, and News Feed. Left Trolls usually pretend to be BLM activists, aiming to divide the democratic party (in this context, being pro-Bernie so that votes are taken away from Hillary). Right trolls imitate Trump supporters, and News Feed handles are “local news aggregators,” typically linking to legitimate news.
For our upcoming analyses, some important variables are:
- author (handle sending the tweet)
- content (text of the tweet)
- language (language of the tweet)
- publish_date (date and time the tweet was sent)
Variable documentation can be found on Github and a more detailed description of the dataset can be found in this fivethirtyeight article.
Because there are 12 datasets containing 2,973,371 tweets sent by 2,848 Twitter handles in total, we will be using three of these datasets (one from a Right troll, one from a Left troll, and one from a News Feed account).
1. Read in Troll Tweets Dataset
# Download the file from github and place in the same project folder
troll_tweets<- read.csv("IRAhandle_tweets_12.csv")
2. Basic Data Cleaning and Exploration
- Remove rows where the tweet was in a language other than English
- Report the dimensions of the dataset
- Create two or three basic exploratory plots of the data (ex. plot of the different locations from which tweets were posted, plot of the account category of a tweet)
# only consider tweets that are in English
troll_tweets1 <- troll_tweets%>%
filter(language == 'English')
#possible ideas for exploratory plots
#see where tweets were being posted from
ggplot(troll_tweets1, aes(x = region)) +
geom_bar() +
theme(axis.text.x = element_text(angle = 90))

#see what kinds of accounts there are
ggplot(troll_tweets1, aes(x = account_category)) +
geom_bar() +
theme(axis.text.x = element_text(angle = 90))

3. Unnest Tokens
We want each row to represent a word from a tweet, rather than an entire tweet.
#separate tweet so that each row gets an individual word: unnest tokens
troll_tweets_untoken <- troll_tweets1 %>%
unnest_tokens(word,content)
troll_tweets_untoken
4. Remove stopwords
#get rid of stopwords (the, and, etc.)
troll_tweets_cleaned <- troll_tweets_untoken %>%
anti_join(stop_words)
## Joining, by = "word"
Take a look at the troll_tweets_cleaned dataset. Are there any other words/letters/numbers that we want to eliminate that weren’t taken care of by stop_words?
#get rid of http, https, t.co, rt, amp, single number digits, and singular letters
troll_tweets_cleaned <- troll_tweets_cleaned %>%
filter(word != '1', word != '2', word != '3', word != '4', word != '5', word != '10', word != 'rt', word != 'amp', word != 'http', !(word %in% letters), word != 'https', word != 't.co')
5. Look at a subset of the tweets to see how often the top words appear.
troll_tweets_small <- troll_tweets_cleaned %>%
count(word) %>%
slice_max(order_by = n, n = 50)
ggplot(troll_tweets_small, aes(y = fct_reorder(word,n), x = n)) +
geom_col()

6. Sentiment Analysis
- Get the sentiments using the “bing” parameter (which classifies words into “positive” or “negative”)
- Report how many positive and negative words there are in the dataset. Are there more positive or negative words, and why do you think this might be?
# look at sentiment
get_sentiments("bing")
# assign a sentiment to each word that has one associated
troll_tweets_sentiment <- troll_tweets_cleaned %>%
inner_join(sentiments)
## Joining, by = "word"
# count the sentiments
troll_tweets_sentiment %>%
count(sentiment)
7. Using the troll_tweets_small dataset, make a wordcloud:
- That is sized by the number of times that a word appears in the tweets
- That is colored by sentiment (positive or negative)
# make a wordcloud where the size of the word is based on the number of times the word appears across the tweets
troll_tweets_small %>%
with(wordcloud(word, n, max.words = 100))

# make a wordcloud colored by sentiment
troll_tweets_sentiment %>%
acast(word ~ sentiment, fill = 0) %>%
comparison.cloud(colors = c("red", "purple"),
max.words = 50)
## Using sentiment as value column: use value.var to override.
## Aggregation function missing: defaulting to length

Are there any words whose categorization as “positive” or “negative” surprised you? trump as positive was suprising but this was probably due to the verb ‘trump’
Projects
Read the project description on the moodle page. Talk to your group members about potential topics.
Task:
Write a short paragraph about ideas you have. If you already have some data sources in mind, you can link to those, but I’m more concerned with you having a topic that you’re interested in investigating right now.
Disease risk classification sounds very interesting to me. Dataset: https://www.kaggle.com/ronitf/heart-disease-uci
“Undoing” bias
Task:
Read this tweet thread by Deb Raji who you may remember from the Coded Bias film. Write a short paragraph that discusses at least one of the misconceptions.
The point made about how race and gender are the least obvious biases to detect was interesting. Since this is legally protected information, you can’t have this data and it’s probably hard to identify proxies for these in different datasets in different contexts. The paper about how racial categories was also interesting. It states that these categories are ill defined and the sample might be misprepresented when comparisons are made across different datasets that are using these poorly defined categories.
LS0tDQp0aXRsZTogJ0Fzc2lnbm1lbnQgIzMnDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiNrbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpDQpgYGANCg0KYGBge3IgbGlicmFyaWVzLCBtZXNzYWdlPUZBTFNFfQ0KIyBTRUUgbW9kZWxkYXRhIHBhY2thZ2UgZm9yIG5ldyBkYXRhc2V0cw0KbGlicmFyeSh0aWR5dmVyc2UpICAgICAgICAgIyBmb3IgZ3JhcGhpbmcgYW5kIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgICMgZm9yIG1vZGVsaW5nDQpsaWJyYXJ5KHN0YWNrcykgICAgICAgICAgICAjIGZvciBzdGFja2luZyBtb2RlbHMNCmxpYnJhcnkobmFuaWFyKSAgICAgICAgICAgICMgZm9yIGV4YW1pbmluZyBtaXNzaW5nIHZhbHVlcyAoTkFzKQ0KbGlicmFyeShsdWJyaWRhdGUpICAgICAgICAgIyBmb3IgZGF0ZSBtYW5pcHVsYXRpb24NCmxpYnJhcnkobW9kZXJuZGl2ZSkgICAgICAgICMgZm9yIEtpbmcgQ291bnR5IGhvdXNpbmcgZGF0YQ0KbGlicmFyeShEQUxFWCkgICAgICAgICAgICAgIyBmb3IgbW9kZWwgaW50ZXJwcmV0YXRpb24gIA0KbGlicmFyeShEQUxFWHRyYSkgICAgICAgICAgIyBmb3IgZXh0ZW5zaW9uIG9mIERBTEVYDQpsaWJyYXJ5KHBhdGNod29yaykgICAgICAgICAjIGZvciBjb21iaW5pbmcgcGxvdHMgbmljZWx5DQpsaWJyYXJ5KGRicGx5cikgICAgICAgICAgICAjIGZvciBTUUwgcXVlcnkgImNoZWF0aW5nIiAtIHBhcnQgb2YgdGlkeXZlcnNlIGJ1dCBuZWVkcyB0byBiZSBsb2FkZWQgc2VwYXJhdGVseQ0KbGlicmFyeShtZHNyKSAgICAgICAgICAgICAgIyBmb3IgYWNjZXNzaW5nIHNvbWUgZGF0YWJhc2VzIC0gZ29lcyB3aXRoIE1vZGVybiBEYXRhIFNjaWVuY2Ugd2l0aCBSIHRleHRib29rDQpsaWJyYXJ5KFJNeVNRTCkgICAgICAgICAgICAjIGZvciBhY2Nlc3NpbmcgTXlTUUwgZGF0YWJhc2VzDQpsaWJyYXJ5KFJTUUxpdGUpICAgICAgICAgICAjIGZvciBhY2Nlc3NpbmcgU1FMaXRlIGRhdGFiYXNlcw0KDQojbWFwcGluZw0KbGlicmFyeShtYXBzKSAgICAgICAgICAgICAgIyBmb3IgYnVpbHQtaW4gbWFwcw0KbGlicmFyeShzZikgICAgICAgICAgICAgICAgIyBmb3IgbWFraW5nIG1hcHMgdXNpbmcgZ2VvbV9zZg0KbGlicmFyeShnZ3RoZW1lcykgICAgICAgICAgIyBMaXNhIGFkZGVkIC0gSSBsaWtlIHRoZW1lX21hcCgpIGZvciBtYXBzIDopDQoNCiN0aWR5dGV4dA0KbGlicmFyeSh0aWR5dGV4dCkgICAgICAgICAgIyBmb3IgdGV4dCBhbmFseXNpcywgdGhlIHRpZHkgd2F5IQ0KbGlicmFyeSh0ZXh0ZGF0YSkgICAgICAgICAgDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeSh3b3JkY2xvdWQpICAgICAgICAgIyBmb3Igd29yZGNsb3VkDQpsaWJyYXJ5KHN0b3B3b3JkcykNCg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkgIyBMaXNhJ3MgZmF2b3JpdGUgdGhlbWUNCg0KbGlicmFyeShsaW1lKQ0KYGBgDQoNCldoZW4geW91IGZpbmlzaCB0aGUgYXNzaWdubWVudCwgcmVtb3ZlIHRoZSBgI2AgZnJvbSB0aGUgb3B0aW9ucyBjaHVuayBhdCB0aGUgdG9wLCBzbyB0aGF0IG1lc3NhZ2VzIGFuZCB3YXJuaW5ncyBhcmVuJ3QgcHJpbnRlZC4gSWYgeW91IGFyZSBnZXR0aW5nIGVycm9ycyBpbiB5b3VyIGNvZGUsIGFkZCBgZXJyb3IgPSBUUlVFYCBzbyB0aGF0IHRoZSBmaWxlIGtuaXRzLiBJIHdvdWxkIHJlY29tbWVuZCBub3QgcmVtb3ZpbmcgdGhlIGAjYCB1bnRpbCB5b3UgYXJlIGNvbXBsZXRlbHkgZmluaXNoZWQuDQoNCiMjIFB1dCBpdCBvbiBHaXRIdWIhICAgICAgICANCg0KRnJvbSBub3cgb24sIEdpdEh1YiBzaG91bGQgYmUgcGFydCBvZiB5b3VyIHJvdXRpbmUgd2hlbiBkb2luZyBhc3NpZ25tZW50cy4gSSByZWNvbW1lbmQgbWFraW5nIGl0IHBhcnQgb2YgeW91ciBwcm9jZXNzIGFueXRpbWUgeW91IGFyZSB3b3JraW5nIGluIFIsIGJ1dCBJJ2xsIG1ha2UgeW91IHNob3cgaXQncyBwYXJ0IG9mIHlvdXIgcHJvY2VzcyBmb3IgYXNzaWdubWVudHMuDQoNCioqVGFzayoqOiBXaGVuIHlvdSBhcmUgZmluaXNoZWQgd2l0aCB0aGUgYXNzaWdubWVudCwgcG9zdCBhIGxpbmsgYmVsb3cgdG8gdGhlIEdpdEh1YiByZXBvIGZvciB0aGUgYXNzaWdubWVudC4gSWYgeW91IHdhbnQgdG8gcG9zdCBpdCB0byB5b3VyIHBlcnNvbmFsIHdlYnNpdGUsIHRoYXQncyBvayAobm90IHJlcXVpcmVkKS4gTWFrZSBzdXJlIHRoZSBsaW5rIGdvZXMgdG8gYSBzcG90IGluIHRoZSByZXBvIHdoZXJlIEkgY2FuIGVhc2lseSBmaW5kIHRoaXMgYXNzaWdubWVudC4gRm9yIGV4YW1wbGUsIGlmIHlvdSBoYXZlIGEgd2Vic2l0ZSB3aXRoIGEgYmxvZyBhbmQgcG9zdCB0aGUgYXNzaWdubWVudCBhcyBhIGJsb2cgcG9zdCwgbGluayB0byB0aGUgcG9zdCdzIGZvbGRlciBpbiB0aGUgcmVwby4gQXMgYW4gZXhhbXBsZSwgSSd2ZSBsaW5rZWQgdG8gbXkgR2l0SHViIHN0YWNraW5nIG1hdGVyaWFsIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vbGxlbmR3YXkvYWRzX3dlYnNpdGUvdHJlZS9tYXN0ZXIvX3Bvc3RzLzIwMjEtMDMtMjItc3RhY2tpbmcpLg0KDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9OaWtldGgyNy9BZHZhbmNlZF9EU18zDQoNCiMjIExvY2FsIEludGVycHJldGFibGUgTWFjaGluZSBMZWFybmluZw0KDQpZb3UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgS2luZyBDb3VudHkgaG91c2UgZGF0YSBhbmQgdGhlIHNhbWUgcmFuZG9tIGZvcmVzdCBtb2RlbCB0byBwcmVkaWN0IGBsb2dfcHJpY2VgIHRoYXQgSSB1c2VkIGluIHRoZSBbdHV0b3JpYWxdKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9wb3N0cy8yMDIxLTAzLTMxLWltbGxvY2FsLykuDQoNCmBgYHtyfQ0KZGF0YSgiaG91c2VfcHJpY2VzIikNCg0KIyBDcmVhdGUgbG9nX3ByaWNlIGFuZCBkcm9wIHByaWNlIHZhcmlhYmxlDQpob3VzZV9wcmljZXMgPC0gaG91c2VfcHJpY2VzICU+JSANCiAgbXV0YXRlKGxvZ19wcmljZSA9IGxvZyhwcmljZSwgYmFzZSA9IDEwKSkgJT4lIA0KICAjIG1ha2UgYWxsIGludGVnZXJzIG51bWVyaWMgLi4uIGZpeGVzIHByZWRpY3Rpb24gcHJvYmxlbQ0KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmludGVnZXIpLCBhcy5udW1lcmljKSkgJT4lIA0KICBzZWxlY3QoLXByaWNlKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMzI3KSAjZm9yIHJlcHJvZHVjaWJpbGl0eQ0KDQojIFJhbmRvbWx5IGFzc2lnbnMgNzUlIG9mIHRoZSBkYXRhIHRvIHRyYWluaW5nLg0KaG91c2Vfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChob3VzZV9wcmljZXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9wID0gLjc1KQ0KaG91c2VfdHJhaW5pbmcgPC0gdHJhaW5pbmcoaG91c2Vfc3BsaXQpDQpob3VzZV90ZXN0aW5nIDwtIHRlc3RpbmcoaG91c2Vfc3BsaXQpDQpgYGANCg0KDQpgYGB7cn0NCnJhbmdlcl9yZWNpcGUgPC0gDQogIHJlY2lwZShmb3JtdWxhID0gbG9nX3ByaWNlIH4gLiwgDQogICAgICAgICBkYXRhID0gaG91c2VfdHJhaW5pbmcpICU+JSANCiAgc3RlcF9kYXRlKGRhdGUsIA0KICAgICAgICAgICAgZmVhdHVyZXMgPSAibW9udGgiKSAlPiUgDQogICMgTWFrZSB0aGVzZSBldmFsdWF0aXZlIHZhcmlhYmxlcywgbm90IGluY2x1ZGVkIGluIG1vZGVsaW5nDQogIHVwZGF0ZV9yb2xlKGFsbF9vZihjKCJpZCIsDQogICAgICAgICAgICAgICAgICAgICAgICJkYXRlIikpLA0KICAgICAgICAgICAgICBuZXdfcm9sZSA9ICJldmFsdWF0aXZlIikNCg0KI2RlZmluZSBtb2RlbA0KcmFuZ2VyX3NwZWMgPC0gDQogIHJhbmRfZm9yZXN0KG10cnkgPSA2LCANCiAgICAgICAgICAgICAgbWluX24gPSAxMCwgDQogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIA0KICBzZXRfZW5naW5lKCJyYW5nZXIiKQ0KDQojY3JlYXRlIHdvcmtmbG93DQpyYW5nZXJfd29ya2Zsb3cgPC0gDQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKHJhbmdlcl9yZWNpcGUpICU+JSANCiAgYWRkX21vZGVsKHJhbmdlcl9zcGVjKSANCg0KI2ZpdCB0aGUgbW9kZWwNCnNldC5zZWVkKDcxMikgIyBmb3IgcmVwcm9kdWNpYmlsaXR5IC0gcmFuZG9tIHNhbXBsaW5nIGluIHJhbmRvbSBmb3Jlc3QgY2hvb3NpbmcgbnVtYmVyIG9mIHZhcmlhYmxlcw0KcmFuZ2VyX2ZpdCA8LSByYW5nZXJfd29ya2Zsb3cgJT4lIA0KICBmaXQoaG91c2VfdHJhaW5pbmcpDQpgYGANCg0KKipUYXNrczoqKg0KDQoNCjEuIENob29zZSAzIG5ldyBvYnNlcnZhdGlvbnMgYW5kIGRvIHRoZSBmb2xsb3dpbmcgZm9yIGVhY2ggb2JzZXJ2YXRpb246ICANCiAgLSBDb25zdHJ1Y3QgYSBicmVhay1kb3duIHBsb3QgdXNpbmcgdGhlIGRlZmF1bHQgb3JkZXJpbmcuIEludGVycHJldCB0aGUgcmVzdWx0aW5nIGdyYXBoLiBXaGljaCB2YXJpYWJsZXMgY29udHJpYnV0ZSBtb3N0IHRvIGVhY2ggb2JzZXJ2YXRpb24ncyBwcmVkaWN0aW9uPyAgDQogIC0gQ29uc3RydWN0IGEgU0hBUCBncmFwaCBhbmQgaW50ZXJwcmV0IGl0LiBEb2VzIGl0IHRlbGwgYSBzaW1pbGFyIHN0b3J5IHRvIHRoZSBicmVhay1kb3duIHBsb3Q/ICANCiAgLSBDb25zdHJ1Y3QgYSBMSU1FIGdyYXBoIChmb2xsb3cgbXkgY29kZSBjYXJlZnVsbHkpLiBIb3cgY2xvc2UgaXMgZWFjaCBvcmlnaW5hbCBwcmVkaWN0aW9uIHRvIHRoZSBwcmVkaWN0aW9uIGZyb20gdGhlIGxvY2FsIG1vZGVsPyBJbnRlcnByZXQgdGhlIHJlc3VsdC4gWW91IGNhbiBhbHNvIHRyeSB1c2luZyBmZXdlciBvciBtb3JlIHZhcmlhYmxlcyBpbiB0aGUgbG9jYWwgbW9kZWwgdGhhbiBJIHVzZWQgaW4gdGhlIGV4YW1wbGUuIA0KICANCmBgYHtyfQ0KcmZfZXhwbGFpbiA8LSANCiAgZXhwbGFpbl90aWR5bW9kZWxzKA0KICAgIG1vZGVsID0gcmFuZ2VyX2ZpdCwNCiAgICBkYXRhID0gaG91c2VfdHJhaW5pbmcgJT4lIHNlbGVjdCgtbG9nX3ByaWNlKSwgDQogICAgeSA9IGhvdXNlX3RyYWluaW5nICU+JSAgcHVsbChsb2dfcHJpY2UpLA0KICAgIGxhYmVsID0gInJmIg0KICApDQpgYGANCiAgDQogIA0KYGBge3J9DQpob3VzZV90ZXN0aW5nDQpuZXdfb2JzMSA8LSBob3VzZV90ZXN0aW5nICU+JSBzbGljZSgzMzMzKSANCm5ld19vYnMyIDwtIGhvdXNlX3Rlc3RpbmcgJT4lIHNsaWNlKDEyMzQpIA0KbmV3X29iczMgPC0gaG91c2VfdGVzdGluZyAlPiUgc2xpY2UoNDMyMSkgDQpgYGANCiAgDQpgYGB7cn0NCiMgUHVsbHMgdG9nZXRoZXIgdGhlIGRhdGEgbmVlZGVkIGZvciB0aGUgYnJlYWstZG93biBwbG90DQpwcF9yZjEgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLA0KICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMSwNCiAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJicmVha19kb3duIikNCnBwX3JmMiA8LSBwcmVkaWN0X3BhcnRzKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMyLA0KICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImJyZWFrX2Rvd24iKQ0KcHBfcmYzIDwtIHByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gbmV3X29iczMsDQogICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpDQojIEJyZWFrLWRvd24gcGxvdA0KcGxvdChwcF9yZjEpDQpwbG90KHBwX3JmMikNCnBsb3QocHBfcmYzKQ0KDQojVE9ETzogSW50ZXJwcmV0DQpgYGANCiAgbGF0aXR1ZGUgYXBwZWFycyB0byBiZSB0aGUgbW9zdCBkb21pbmFudCBwcmVkaWN0b3Igd2l0aCBhbGwgcHJlZGljdGlvbnMuIHNxZnRfbGl2aW5nLCBzcWZ0X2Fib3ZlIGFuZCBncmFkZSBhbHNvIHNlZW0gdG8gYmUgaW1wb3J0YW50IHByZWRpY3RvcnMuDQogIA0KYGBge3J9DQpyZl9zaGFwMSA8LXByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMxLA0KICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIEIgPSAxMCAjbnVtYmVyIG9mIHJlb3JkZXJpbmdzIC0gc3RhcnQgc21hbGwNCikNCnJmX3NoYXAyIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gbmV3X29iczIsDQogICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInNoYXAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwICNudW1iZXIgb2YgcmVvcmRlcmluZ3MgLSBzdGFydCBzbWFsbA0KKQ0KcmZfc2hhcDMgPC1wcmVkaWN0X3BhcnRzKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMywNCiAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAic2hhcCIsDQogICAgICAgICAgICAgICAgICAgICAgICBCID0gMTAgI251bWJlciBvZiByZW9yZGVyaW5ncyAtIHN0YXJ0IHNtYWxsDQopDQpwbG90KHJmX3NoYXAxKQ0KcGxvdChyZl9zaGFwMikNCnBsb3QocmZfc2hhcDMpDQpgYGANClllcywgdGhlIHByZWRpY3RvcnMgd2hpY2ggYXJlIHNob3duIHRvIGJlIHNpZ25pZmNhbnQgYXJlIHNpbWlsYXIgaW4gdGhlIGJyZWFrZG93biBwbG90IGFuZCB0aGUgU0hBUCBwbG90Lg0KICANCiAgDQpgYGB7cn0NCnNldC5zZWVkKDIpDQoNCiMgTkVFRCB0aGVzZSB0d28gbGluZXMgb2YgY29kZSBhbHdheXMhDQojIFRoZXkgbWFrZSBzdXJlIG91ciBleHBsYWluZXIgaXMgZGVmaW5lZCBjb3JyZWN0bHkgdG8gdXNlIGluIHRoZSBuZXh0IHN0ZXANCm1vZGVsX3R5cGUuZGFsZXhfZXhwbGFpbmVyIDwtIERBTEVYdHJhOjptb2RlbF90eXBlLmRhbGV4X2V4cGxhaW5lcg0KcHJlZGljdF9tb2RlbC5kYWxleF9leHBsYWluZXIgPC0gREFMRVh0cmE6OnByZWRpY3RfbW9kZWwuZGFsZXhfZXhwbGFpbmVyDQoNCmxpbWVfcmYxIDwtIHByZWRpY3Rfc3Vycm9nYXRlKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMxICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtbG9nX3ByaWNlKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3Blcm11dGF0aW9ucyA9IDEwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibGltZSIpDQoNCmxpbWVfcmYyIDwtIHByZWRpY3Rfc3Vycm9nYXRlKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMyICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtbG9nX3ByaWNlKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3Blcm11dGF0aW9ucyA9IDEwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibGltZSIpDQoNCmxpbWVfcmYzIDwtIHByZWRpY3Rfc3Vycm9nYXRlKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMzICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtbG9nX3ByaWNlKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3Blcm11dGF0aW9ucyA9IDEwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibGltZSIpDQoNCnBsb3QobGltZV9yZjEpICsNCiAgbGFicyh4ID0gIlZhcmlhYmxlIikNCnBsb3QobGltZV9yZjIpICsNCiAgbGFicyh4ID0gIlZhcmlhYmxlIikNCnBsb3QobGltZV9yZjMpICsNCiAgbGFicyh4ID0gIlZhcmlhYmxlIikNCg0KYGBgDQogIA0KICANCjIuIERlc2NyaWJlIGhvdyB5b3Ugd291bGQgdXNlIHRoZSBpbnRlcnByZXRhYmxlIG1hY2hpbmUgbGVhcm5pbmcgdG9vbHMgd2UndmUgbGVhcm5lZCAoYm90aCBsb2NhbCBhbmQgZ2xvYmFsKSBpbiBmdXR1cmUgbWFjaGluZSBsZWFybmluZyBwcm9qZWN0cz8gSG93IGRvZXMgZWFjaCBvZiB0aGVtIGhlbHAgeW91Pw0KDQpUaGVzZSB0b29scyBoZWxwIHVzIGlkZW50aWZ5IHRoZSBtb3N0IGltcG9ydGFudCBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbCBhbmQgdGhlIG5hdHVyZSBvZiB0aGVpciByZWxhdGlvbnNoaXAgd2l0aCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgd2hpY2ggaGVscHMgdXMgaW50dWl0aXZlbHkgdW5kZXJzdGFuZCBob3cgdGhlIHByZWRpY3Rpb25zIGFyZSBiZWluZyBtYWRlIHdoaWNoIGhlbHBzIHVzIG1ha2UgYmV0dGVyIG1vZGVsIGNob2ljZXMuDQoNCg0KI1dyaXRpbmc6VE9ETyANCg0KIyMgU1FMDQoNCllvdSB3aWxsIHVzZSB0aGUgYGFpcmxpbmVzYCBkYXRhIGZyb20gdGhlIFNRTCBkYXRhYmFzZSB0aGF0IEkgdXNlZCBpbiB0aGUgZXhhbXBsZSBpbiB0aGUgW3R1dG9yaWFsXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0yOS1zcWxpbnIvKS4gQmUgc3VyZSB0byBpbmNsdWRlIHRoZSBjaHVuayB0byBjb25uZWN0IHRvIHRoZSBkYXRhYmFzZSBoZXJlLiBBbmQsIHdoZW4geW91IGFyZSBmaW5pc2hlZCwgZGlzY29ubmVjdC4gWW91IG1heSBuZWVkIHRvIHJlY29ubmVjdCB0aHJvdWdob3V0IGFzIGl0IHRpbWVzIG91dCBhZnRlciBhIHdoaWxlLg0KDQpgYGB7cn0NCmNvbl9haXIgPC0gZGJDb25uZWN0KFJNeVNRTDo6TXlTUUwoKSwgDQogICAgICAgICAgICAgICAgICAgICBkYm5hbWUgPSAiYWlybGluZXMiLCANCiAgICAgICAgICAgICAgICAgICAgIGhvc3QgPSAibWRzci5jZGM3dGdra3FkMG4udXMtZWFzdC0xLnJkcy5hbWF6b25hd3MuY29tIiwgDQogICAgICAgICAgICAgICAgICAgICB1c2VyID0gIm1kc3JfcHVibGljIiwgDQogICAgICAgICAgICAgICAgICAgICBwYXNzd29yZCA9ICJJbWhzbWZsTURTd1IiKQ0KYGBgDQoNCmBgYHtyfQ0KZGJMaXN0VGFibGVzKGNvbl9haXIpDQpgYGANCg0KKipUYXNrcyoqOg0KMS4gQ3JlYXRlIGEgU1FMIGNodW5rIGFuZCBhbiBlcXVpdmFsZW50IFIgY29kZSBjaHVuayB0aGF0IGRvZXMgdGhlIGZvbGxvd2luZzogZm9yIDIwMTcgLCBmb3IgZWFjaCBhaXJwb3J0ICh3aXRoIGl0cyBuYW1lLCBub3QgY29kZSksIGFuZCBtb250aCBmaW5kIHRoZSB0b3RhbCBudW1iZXIgb2YgZGVwYXJ0aW5nIGZsaWdodHMsIHRoZSBhdmVyYWdlIGRpc3RhbmNlIG9mIHRoZSBmbGlnaHQsIGFuZCB0aGUgcHJvcG9ydGlvbiBvZiBmbGlnaHRzIHRoYXQgYXJyaXZlZCBtb3JlIHRoYW4gMjAgbWludXRlcyBsYXRlLiBJbiB0aGUgUiBjb2RlIGNodW5rLCB3cml0ZSB0aGlzIG91dCB0byBhIGRhdGFzZXQuIChISU5UOiAxLiBzdGFydCBzbWFsbCEgMi4geW91IG1heSB3YW50IHRvIGRvIHRoZSBSIHBhcnQgZmlyc3QgYW5kIHVzZSBpdCB0byAiY2hlYXQiIGludG8gdGhlIFNRTCBjb2RlKS4oTmV3KQ0KDQogIC0gV2l0aCB0aGUgZGF0YXNldCB5b3Ugd3JvdGUgb3V0LCBjcmVhdGUgYSBncmFwaCB0aGF0IGhlbHBzIGlsbHVzdHJhdGUgdGhlICJ3b3JzdCIgYWlycG9ydHMgaW4gdGVybXMgb2YgbGF0ZSBhcnJpdmFscy4gWW91IGhhdmUgc29tZSBmcmVlZG9tIGluIGhvdyB5b3UgZGVmaW5lIHdvcnN0IGFuZCB5b3UgbWF5IHdhbnQgdG8gY29uc2lkZXIgc29tZSBvZiB0aGUgb3RoZXIgdmFyaWFibGVzIHlvdSBjb21wdXRlZC4gRG8gc29tZSB0aGVtaW5nIHRvIG1ha2UgeW91ciBncmFwaCBsb29rIGdsYW1vcm91cyAodGhvc2Ugb2YgeW91IHdobyB3ZXJlbid0IGluIG15IGludHJvIGRhdGEgc2NpZW5jZSBjbGFzcyB0aGlzIHllYXIgbWF5IHdhbnQgdG8gd2F0Y2ggV2lsbCBDaGFzZSdzIFtHbGFtb3VyIG9mIEdyYXBoaWNzXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWg1Y1RhY2FXRTZJKSB0YWxrIGZvciBpbnNwaXJhdGlvbikuICANCiAgDQogIC0gQWx0aG91Z2ggeW91ciBncmFwaCB3YXMgdHJ1bHkgaW5zcGlyYXRpb25hbCwgeW91J3ZlIGJlZW4gcmVxdWVzdGVkIHRvICJib2lsIGl0IGRvd24gdG8gYSBmZXcgbnVtYmVycy4iIFNvbWUgcGVvcGxlIGp1c3QgZG9uJ3QgYXBwcmVjaWF0ZSBhbGwgdGhhdCBlZmZvcnQgeW91IHB1dCBpbi4gQW5kLCB5b3UgbmVlZCB0byB1c2UgdGhlIGFscmVhZHkgc3VtbWFyaXplZCBkYXRhIHRoYXQgeW91IGFscmVhZHkgcHVsbGVkIGluIGZyb20gU1FMLiBDcmVhdGUgYSB0YWJsZSB3aXRoIDYgb3IgZmV3ZXIgcm93cyBhbmQgMyBvciBmZXdlciBjb2x1bW5zIHRoYXQgc3VtbWFyaXplcyB3aGljaCBhaXJwb3J0IGlzIHRoZSAid29yc3QiIGluIHRlcm1zIG9mIGxhdGUgYXJyaXZhbHMuIEJlIGNhcmVmdWwgd2l0aCB5b3VyIGNhbGN1bGF0aW9ucy4gWW91IG1heSBjb25zaWRlciB1c2luZyB0aGUgYGthYmxlYCwgYGthYmxlRXh0cmFgLCBvciBgZ3RgIHBhY2thZ2VzIHRvIG1ha2UgeW91ciB0YWJsZSBsb29rIHRydWx5IHNwZWN0YWN1bGFyLg0KDQoNCmBgYHtyfQ0KIyBmb3IgMjAxNyAsIGZvciBlYWNoIGFpcnBvcnQgKHdpdGggaXRzIG5hbWUsIG5vdCBjb2RlKSwgYW5kIG1vbnRoIGZpbmQgdGhlIHRvdGFsIG51bWJlciBvZiBkZXBhcnRpbmcgZmxpZ2h0cywgdGhlIGF2ZXJhZ2UgZGlzdGFuY2Ugb2YgdGhlIGZsaWdodCwgYW5kIHRoZSBwcm9wb3J0aW9uIG9mIGZsaWdodHMgdGhhdCBhcnJpdmVkIG1vcmUgdGhhbiAyMCBtaW51dGVzIGxhdGUuDQoNCnRibChjb25fYWlyLCAiZmxpZ2h0cyIpICU+JSANCiAgZmlsdGVyKHllYXIgPT0gMjAxNykgJT4lIA0KICBncm91cF9ieShvcmlnaW4sbW9udGgpICU+JSANCiAgc3VtbWFyaXNlKGRlcF9mbGlnaHRzID0gbigpLCBhdmdfZGlzdGFuY2UgPSBtZWFuKGFpcl90aW1lKSwgIHByb3BfbGF0ZV9vdmVyMjAgPSBtZWFuKGFycl9kZWxheSA+IDIwKSkgJT4lIA0KICBpbm5lcl9qb2luKCB0YmwoY29uX2FpciwiYWlycG9ydHMiKSwgYnkgPSBjKCJvcmlnaW4iPSJmYWEiKSkgJT4lIA0KICBzZWxlY3Qob3JpZ2luOiBuYW1lKSAlPiUgIA0KICBzaG93X3F1ZXJ5KCkNCg0KbGF0ZV9haXJwb3J0IDwtIHRibChjb25fYWlyLCAiZmxpZ2h0cyIpICU+JSANCiAgZmlsdGVyKHllYXIgPT0gMjAxNykgJT4lIA0KICBncm91cF9ieShvcmlnaW4sbW9udGgpICU+JSANCiAgc3VtbWFyaXNlKGRlcF9mbGlnaHRzID0gbigpLCBhdmdfZGlzdGFuY2UgPSBtZWFuKGFpcl90aW1lKSwgIHByb3BfbGF0ZV9vdmVyMjAgPSBtZWFuKGFycl9kZWxheSA+IDIwKSkgJT4lIA0KICBpbm5lcl9qb2luKCB0YmwoY29uX2FpciwiYWlycG9ydHMiKSwgYnkgPSBjKCJvcmlnaW4iPSJmYWEiKSkgJT4lIA0KICBzZWxlY3Qob3JpZ2luOiBuYW1lKQ0KYGBgDQoNCg0KYGBge3NxbCBjb25uZWN0aW9uPWNvbl9haXJ9DQpTRUxFQ1QgYG9yaWdpbmAsIGBtb250aGAsIGBkZXBfZmxpZ2h0c2AsIGBhdmdfZGlzdGFuY2VgLCBgcHJvcF9sYXRlX292ZXIyMGAsIGBuYW1lYA0KRlJPTSAoU0VMRUNUIGBvcmlnaW5gLCBgbW9udGhgLCBgZGVwX2ZsaWdodHNgLCBgYXZnX2Rpc3RhbmNlYCwgYHByb3BfbGF0ZV9vdmVyMjBgLCBgbmFtZWAsIGBsYXRgLCBgbG9uYCwgYGFsdGAsIGB0emAsIGBkc3RgLCBgY2l0eWAsIGBjb3VudHJ5YA0KRlJPTSAoU0VMRUNUIGBvcmlnaW5gLCBgbW9udGhgLCBDT1VOVCgqKSBBUyBgZGVwX2ZsaWdodHNgLCBBVkcoYGFpcl90aW1lYCkgQVMgYGF2Z19kaXN0YW5jZWAsIEFWRyhgYXJyX2RlbGF5YCA+IDIwLjApIEFTIGBwcm9wX2xhdGVfb3ZlcjIwYA0KRlJPTSBgZmxpZ2h0c2ANCldIRVJFIChgeWVhcmAgPSAyMDE3LjApDQpHUk9VUCBCWSBgb3JpZ2luYCwgYG1vbnRoYCkgYExIU2ANCklOTkVSIEpPSU4gYGFpcnBvcnRzYCBBUyBgUkhTYA0KT04gKGBMSFNgLmBvcmlnaW5gID0gYFJIU2AuYGZhYWApDQopIGBxMDFgDQpgYGANCg0KYGBge3J9DQpsYXRlX2FpcnBvcnQgJT4lDQogIGdyb3VwX2J5KG5hbWUpICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fcHJvcCA9IG1lYW4ocHJvcF9sYXRlX292ZXIyMCkpICU+JSANCiAgYXJyYW5nZShkZXNjKG1lYW5fcHJvcCkpICU+JSANCiAgaGVhZCgzMCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBtZWFuX3Byb3AsDQogICAgICAgICAgICAgeSA9IChmY3RfcmVvcmRlcihuYW1lLG1lYW5fcHJvcCkpKSkgKw0KICBnZW9tX2NvbChmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIkFpcnBvcnRzIHdpdGggbGFyZ2VzdCAlIG9mIGRlcGFydGluZyBmbGlnaHRzIHRoYXQgd2VyZSBtb3JlIHRoYW4gMjBtaW4gbGF0ZSIpKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyAtIEFsdGhvdWdoIHlvdXIgZ3JhcGggd2FzIHRydWx5IGluc3BpcmF0aW9uYWwsIHlvdSd2ZSBiZWVuIHJlcXVlc3RlZCB0byAiYm9pbCBpdCBkb3duIHRvIGEgZmV3IG51bWJlcnMuIiBTb21lIHBlb3BsZSBqdXN0IGRvbid0IGFwcHJlY2lhdGUgYWxsIHRoYXQgZWZmb3J0IHlvdSBwdXQgaW4uIEFuZCwgeW91IG5lZWQgdG8gdXNlIHRoZSBhbHJlYWR5IHN1bW1hcml6ZWQgZGF0YSB0aGF0IHlvdSBhbHJlYWR5IHB1bGxlZCBpbiBmcm9tIFNRTC4gQ3JlYXRlIGEgdGFibGUgd2l0aCA2IG9yIGZld2VyIHJvd3MgYW5kIDMgb3IgZmV3ZXIgY29sdW1ucyB0aGF0IHN1bW1hcml6ZXMgd2hpY2ggYWlycG9ydCBpcyB0aGUgIndvcnN0IiBpbiB0ZXJtcyBvZiBsYXRlIGFycml2YWxzLiBCZSBjYXJlZnVsIHdpdGggeW91ciBjYWxjdWxhdGlvbnMuIFlvdSBtYXkgY29uc2lkZXIgdXNpbmcgdGhlIGBrYWJsZWAsIGBrYWJsZUV4dHJhYCwgb3IgYGd0YCBwYWNrYWdlcyB0byBtYWtlIHlvdXIgdGFibGUgbG9vayB0cnVseSBzcGVjdGFjdWxhci4NCmxpYnJhcnkoZ3QpDQpndChsYXRlX2FpcnBvcnQgJT4lDQogIGdyb3VwX2J5KG5hbWUpICU+JSANCiAgc3VtbWFyaXNlKGF2ZXJhZ2VfbGF0ZV9wZXJjZW50YWdlPSBtZWFuKHByb3BfbGF0ZV9vdmVyMjApKjEwMCkgJT4lIA0KICBhcnJhbmdlKGRlc2MoYXZlcmFnZV9sYXRlX3BlcmNlbnRhZ2UpKSAlPiUgDQogIGhlYWQoNikpDQpgYGANCg0KICANCjIuIENvbWUgdXAgd2l0aCB5b3VyIG93biBpbnRlcmVzdGluZyBxdWVzdGlvbiB0aGF0IGRhdGEgaW4gdGhlIGFpcmxpbmVzIGRhdGFiYXNlIGNhbiBoZWxwIHlvdSBhbnN3ZXIuIFdyaXRlIGEgU1FMIHF1ZXJ5IGFuZCBlcXVpdmFsZW50IFIgY29kZSBjaHVuayB0byBleHRyYWN0IHRoZSBkYXRhIHlvdSBuZWVkIGFuZCBjcmVhdGUgYW4gZWxlZ2FudCBncmFwaCB0byBoZWxwIGFuc3dlciB0aGUgcXVlc3Rpb24uIEJlIHN1cmUgdG8gd3JpdGUgZG93biB0aGUgcXVlc3Rpb24gc28gaXQgaXMgY2xlYXIuIA0KIA0KDQpRdWVzdGlvbjogV2hhdCdzIHRoZSBidXNpZXN0IGNhcnJpZXIgMjAxMCAtIDIwMTc/DQoNCmBgYHtyfQ0KIGNhcnJpZXJfdG90X2ZsaWdodHMgPC10YmwoY29uX2FpciwgImZsaWdodHMiKSAlPiUgDQogIGdyb3VwX2J5KGNhcnJpZXIpICU+JSANCiAgc3VtbWFyaXNlKHRvdF9mbGlnaHRzID0gbigpKSAlPiUgDQogIGlubmVyX2pvaW4oIHRibChjb25fYWlyLCJjYXJyaWVycyIpLCBieSA9IGMoImNhcnJpZXIiPSJjYXJyaWVyIikpIA0KDQp0YmwoY29uX2FpciwgImZsaWdodHMiKSAlPiUgDQogIGdyb3VwX2J5KGNhcnJpZXIpICU+JSANCiAgc3VtbWFyaXNlKHRvdF9mbGlnaHRzID0gbigpKSAlPiUgDQogIGlubmVyX2pvaW4oIHRibChjb25fYWlyLCJjYXJyaWVycyIpLCBieSA9IGMoImNhcnJpZXIiPSJjYXJyaWVyIikpICU+JSANCiAgc2hvd19xdWVyeSgpDQpgYGANCg0KYGBge3NxbCBjb25uZWN0aW9uPWNvbl9haXJ9DQpTRUxFQ1QgYExIU2AuYGNhcnJpZXJgIEFTIGBjYXJyaWVyYCwgYHRvdF9mbGlnaHRzYCwgYG5hbWVgDQpGUk9NIChTRUxFQ1QgYGNhcnJpZXJgLCBDT1VOVCgqKSBBUyBgdG90X2ZsaWdodHNgDQpGUk9NIGBmbGlnaHRzYA0KR1JPVVAgQlkgYGNhcnJpZXJgKSBgTEhTYA0KSU5ORVIgSk9JTiBgY2FycmllcnNgIEFTIGBSSFNgDQpPTiAoYExIU2AuYGNhcnJpZXJgID0gYFJIU2AuYGNhcnJpZXJgKQ0KYGBgDQoNCmBgYHtyfQ0KY2Fycmllcl90b3RfZmxpZ2h0cyAlPiUgDQogIGdncGxvdChhZXMoeCA9IHRvdF9mbGlnaHRzLCB5PSBmY3RfcmVvcmRlcihuYW1lLCB0b3RfZmxpZ2h0cykpKSArDQogIGdlb21fY29sKGZpbGwgPSAib2xpdmVkcmFiMiIpKyANCiAgbGFicyggeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIkNhcnJpZXJzIHdpdGggdGhlIG1vc3QgZmxpZ2h0cyAoMjAxMCAtMjAxNykiKSsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgRnVuY3Rpb24gRnJpZGF5DQoNCklmIHlvdSBuZWVkIHRvIHJldmlzaXQgdGhlIG1hdGVyaWFsLCBpdCBpcyBwb3N0ZWQgb24gdGhlIG1vb2RsZSBwYWdlLiBJJ3ZlIHRyaWVkIHRvIGFkZCBhbGwgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMgdG8gdGhlIHRvcCwgYnV0IEkgbWF5IGhhdmUgbWlzc2VkIHNvbWV0aGluZy4NCg0KKipgZ2VvbV9zZigpYCB0YXNrcyoqOg0KDQpVc2luZyB0aGUgZXhhbXBsZSBmcm9tIGNsYXNzIHRoYXQgd2UgcHJlc2VudGVkIGFzIGEgYmFzZWxpbmUgKG9yIHlvdXIgb3duIGlmIHlvdSByZWFsbHkgd2FudCB0byBiZSBhbWJpdGlvdXMpLCB0cnkgdG8gYWRkIHRoZSBmb2xsb3dpbmcgY29tcG9uZW50cyB0byB0aGUgbWFwIG9mIHRoZSBjb250aWd1b3VzIFVuaXRlZCBTdGF0ZXM6DQoNCjEuCUNoYW5nZSB0aGUgY29sb3Igc2NoZW1lIG9mIHRoZSBtYXAgZnJvbSB0aGUgZGVmYXVsdCBibHVlIChvbmUgb3B0aW9uIGNvdWxkIGJlIHZpcmlkaXMpLg0KMi4JQWRkIGEgZG90IChvciBhbnkgc3ltYm9sIHlvdSB3YW50KSB0byB0aGUgY2VudHJvaWQgb2YgZWFjaCBzdGF0ZS4NCjMuCUFkZCBhIGxheWVyIG9udG8gdGhlIG1hcCB3aXRoIHRoZSBjb3VudGllcy4NCjQuCUNoYW5nZSB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIG1hcCB0byB6b29tIGluIG9uIHlvdXIgZmF2b3JpdGUgc3RhdGUuDQoNCkhpbnQ6IGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvci8yMDE4LzEwLzI1L2dncGxvdDItc2YtMi5odG1sIGlzIGEgdXNlZnVsIHJlZmVyZW5jZSBmb3Igc29tZSBvZiB0aGUgcXVlc3Rpb25zDQoNCmBgYHtyfQ0KbGlicmFyeShsd2dlb20pDQoNCmBgYA0KDQoNCmBgYHtyfQ0Kc3RhdGUgPC0gc3RfYXNfc2YobWFwczo6bWFwKCJzdGF0ZSIscGxvdCA9IEZBTFNFLGZpbGwgPSBUUlVFKSkNCg0KYGBgDQpgYGB7cn0NCnN0YXRlIDwtIHN0YXRlICU+JQ0KICBtdXRhdGUoYXJlYSA9IGFzLm51bWVyaWMoc3RfYXJlYShzdGF0ZSkpKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RhdGUgPC0gY2JpbmQoc3RhdGUsIHN0X2Nvb3JkaW5hdGVzKHN0X2NlbnRyb2lkKHN0YXRlKSkpDQoNCnN0YXRlICU+JSANCiAgZmlsdGVyKElEID09ICJtaW5uZXNvdGEiKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHN0YXRlKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKw0KICBjb29yZF9zZih4bGltID0gYygtMTI2LCAtNjUpLA0KICAgICAgICAgICAgIHlsaW0gPSBjKDI0LCA1MCksDQogICAgICAgICAgICAgZXhwYW5kID0gRkFMU0UpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gWCwgeSA9IFkpLCBzaXplID0gMSwgc2hhcGUgPSA0KSsNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInZpcmlkaXMiKQ0KIA0KYGBgDQoNCmBgYHtyfQ0KY291bnR5IDwtIHN0X2FzX3NmKG1hcCgiY291bnR5IiwgcGxvdCA9IEZBTFNFLCBmaWxsID0gVFJVRSkpDQoNCmNvdW50eSA8LSBjb3VudHkgJT4lDQogIG11dGF0ZShhcmVhID0gYXMubnVtZXJpYyhzdF9hcmVhKGNvdW50eSkpKQ0KDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHN0YXRlKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3VudHksIGFlcyhmaWxsID0gYXJlYSkpKw0KICBjb29yZF9zZih4bGltID0gYygtMTI3LCAtNjMpLA0KICAgICAgICAgICAgIHlsaW0gPSBjKDI0LCA1MSksDQogICAgICAgICAgICAgZXhwYW5kID0gRkFMU0UpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gWCwgeSA9IFkpLCBzaXplID0gMSwgc2hhcGUgPSA0LCBjb2xvciA9ICJ3aGl0ZSIpKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAidmlyaWRpcyIpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHN0YXRlKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3VudHksIGFlcyhmaWxsID0gYXJlYSkpKw0KICBjb29yZF9zZih4bGltID0gYygtOTYuNSwgLTg5LjUpLA0KICAgICAgICAgICAgIHlsaW0gPSBjKDQ0LCA0OS41KSwNCiAgICAgICAgICAgICBleHBhbmQgPSBGQUxTRSkrDQogIGdlb21fcG9pbnQoYWVzKHggPSBYLCB5ID0gWSksIHNpemUgPSAyLCBzaGFwZSA9IDQsIGNvbG9yID0gIndoaXRlIikrDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJ2aXJpZGlzIikNCmBgYA0KDQoNCioqYHRpZHl0ZXh0YCB0YXNrcyoqOg0KDQpOb3cgeW91IHdpbGwgdHJ5IHVzaW5nIHRpZHl0ZXh0IG9uIGEgbmV3IGRhdGFzZXQgYWJvdXQgUnVzc2lhbiBUcm9sbCB0d2VldHMuDQoNCiMjIyMgUmVhZCBhYm91dCB0aGUgZGF0YQ0KDQpUaGVzZSBhcmUgdHdlZXRzIGZyb20gVHdpdHRlciBoYW5kbGVzIHRoYXQgYXJlIGNvbm5lY3RlZCB0byB0aGUgSW50ZXJuZXQgUmVzZWFyY2ggQWdlbmN5IChJUkEpLCBhIFJ1c3NpYW4gInRyb2xsIGZhY3RvcnkuIiAgVGhlIG1ham9yaXR5IG9mIHRoZXNlIHR3ZWV0cyB3ZXJlIHBvc3RlZCBmcm9tIDIwMTUtMjAxNywgYnV0IHRoZSBkYXRhc2V0cyBlbmNvbXBhc3MgdHdlZXRzIGZyb20gRmVicnVhcnkgMjAxMiB0byBNYXkgMjAxOC4NCg0KVGhyZWUgb2YgdGhlIG1haW4gY2F0ZWdvcmllcyBvZiB0cm9sbCB0d2VldCB0aGF0IHdlIHdpbGwgYmUgZm9jdXNpbmcgb24gYXJlIExlZnQgVHJvbGxzLCBSaWdodCBUcm9sbHMsIGFuZCBOZXdzIEZlZWQuICAqKkxlZnQgVHJvbGxzKiogdXN1YWxseSBwcmV0ZW5kIHRvIGJlIEJMTSBhY3RpdmlzdHMsIGFpbWluZyB0byBkaXZpZGUgdGhlIGRlbW9jcmF0aWMgcGFydHkgKGluIHRoaXMgY29udGV4dCwgYmVpbmcgcHJvLUJlcm5pZSBzbyB0aGF0IHZvdGVzIGFyZSB0YWtlbiBhd2F5IGZyb20gSGlsbGFyeSkuICAqKlJpZ2h0IHRyb2xscyoqIGltaXRhdGUgVHJ1bXAgc3VwcG9ydGVycywgYW5kICoqTmV3cyBGZWVkKiogaGFuZGxlcyBhcmUgImxvY2FsIG5ld3MgYWdncmVnYXRvcnMsIiB0eXBpY2FsbHkgbGlua2luZyB0byBsZWdpdGltYXRlIG5ld3MuDQoNCkZvciBvdXIgdXBjb21pbmcgYW5hbHlzZXMsIHNvbWUgaW1wb3J0YW50IHZhcmlhYmxlcyBhcmU6DQoNCiAgKiAqKmF1dGhvcioqIChoYW5kbGUgc2VuZGluZyB0aGUgdHdlZXQpDQogICogKipjb250ZW50KiogKHRleHQgb2YgdGhlIHR3ZWV0KQ0KICAqICoqbGFuZ3VhZ2UqKiAobGFuZ3VhZ2Ugb2YgdGhlIHR3ZWV0KQ0KICAqICoqcHVibGlzaF9kYXRlKiogKGRhdGUgYW5kIHRpbWUgdGhlIHR3ZWV0IHdhcyBzZW50KQ0KDQpWYXJpYWJsZSBkb2N1bWVudGF0aW9uIGNhbiBiZSBmb3VuZCBvbiBbR2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vZml2ZXRoaXJ0eWVpZ2h0L3J1c3NpYW4tdHJvbGwtdHdlZXRzLykgYW5kIGEgbW9yZSBkZXRhaWxlZCBkZXNjcmlwdGlvbiBvZiB0aGUgZGF0YXNldCBjYW4gYmUgZm91bmQgaW4gdGhpcyBbZml2ZXRoaXJ0eWVpZ2h0IGFydGljbGVdKGh0dHBzOi8vZml2ZXRoaXJ0eWVpZ2h0LmNvbS9mZWF0dXJlcy93aHktd2VyZS1zaGFyaW5nLTMtbWlsbGlvbi1ydXNzaWFuLXRyb2xsLXR3ZWV0cy8pLg0KDQpCZWNhdXNlIHRoZXJlIGFyZSAxMiBkYXRhc2V0cyBjb250YWluaW5nIDIsOTczLDM3MSB0d2VldHMgc2VudCBieSAyLDg0OCBUd2l0dGVyIGhhbmRsZXMgaW4gdG90YWwsIHdlIHdpbGwgYmUgdXNpbmcgdGhyZWUgb2YgdGhlc2UgZGF0YXNldHMgKG9uZSBmcm9tIGEgUmlnaHQgdHJvbGwsIG9uZSBmcm9tIGEgTGVmdCB0cm9sbCwgYW5kIG9uZSBmcm9tIGEgTmV3cyBGZWVkIGFjY291bnQpLg0KDQpcDQpcDQoNCioqMS4gUmVhZCBpbiBUcm9sbCBUd2VldHMgRGF0YXNldCoqDQoNCmBgYHtyfQ0KIyBEb3dubG9hZCB0aGUgZmlsZSBmcm9tIGdpdGh1YiBhbmQgcGxhY2UgaW4gdGhlIHNhbWUgcHJvamVjdCBmb2xkZXINCnRyb2xsX3R3ZWV0czwtIHJlYWQuY3N2KCJJUkFoYW5kbGVfdHdlZXRzXzEyLmNzdiIpDQpgYGANCg0KXA0KXA0KDQoqKjIuIEJhc2ljIERhdGEgQ2xlYW5pbmcgYW5kIEV4cGxvcmF0aW9uKioNCg0KICBhLiBSZW1vdmUgcm93cyB3aGVyZSB0aGUgdHdlZXQgd2FzIGluIGEgbGFuZ3VhZ2Ugb3RoZXIgdGhhbiBFbmdsaXNoDQogIGIuIFJlcG9ydCB0aGUgZGltZW5zaW9ucyBvZiB0aGUgZGF0YXNldA0KICBjLiBDcmVhdGUgdHdvIG9yIHRocmVlIGJhc2ljIGV4cGxvcmF0b3J5IHBsb3RzIG9mIHRoZSBkYXRhIChleC4gcGxvdCBvZiB0aGUgZGlmZmVyZW50IGxvY2F0aW9ucyBmcm9tIHdoaWNoIHR3ZWV0cyB3ZXJlIHBvc3RlZCwgcGxvdCBvZiB0aGUgYWNjb3VudCBjYXRlZ29yeSBvZiBhIHR3ZWV0KQ0KICANCmBgYHtyfQ0KIyBvbmx5IGNvbnNpZGVyIHR3ZWV0cyB0aGF0IGFyZSBpbiBFbmdsaXNoIA0KdHJvbGxfdHdlZXRzMSA8LSB0cm9sbF90d2VldHMlPiUNCiAgZmlsdGVyKGxhbmd1YWdlID09ICdFbmdsaXNoJykNCmBgYA0KDQpgYGB7cn0NCiNwb3NzaWJsZSBpZGVhcyBmb3IgZXhwbG9yYXRvcnkgcGxvdHMNCiNzZWUgd2hlcmUgdHdlZXRzIHdlcmUgYmVpbmcgcG9zdGVkIGZyb20NCmdncGxvdCh0cm9sbF90d2VldHMxLCBhZXMoeCA9IHJlZ2lvbikpICsNCiAgZ2VvbV9iYXIoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KI3NlZSB3aGF0IGtpbmRzIG9mIGFjY291bnRzIHRoZXJlIGFyZQ0KZ2dwbG90KHRyb2xsX3R3ZWV0czEsIGFlcyh4ID0gYWNjb3VudF9jYXRlZ29yeSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KYGBgDQoNClwNClwNCg0KKiozLiBVbm5lc3QgVG9rZW5zKioNCg0KV2Ugd2FudCBlYWNoIHJvdyB0byByZXByZXNlbnQgYSB3b3JkIGZyb20gYSB0d2VldCwgcmF0aGVyIHRoYW4gYW4gZW50aXJlIHR3ZWV0Lg0KDQpgYGB7cn0NCiNzZXBhcmF0ZSB0d2VldCBzbyB0aGF0IGVhY2ggcm93IGdldHMgYW4gaW5kaXZpZHVhbCB3b3JkOiB1bm5lc3QgdG9rZW5zDQp0cm9sbF90d2VldHNfdW50b2tlbiA8LSB0cm9sbF90d2VldHMxICU+JQ0KICB1bm5lc3RfdG9rZW5zKHdvcmQsY29udGVudCkNCnRyb2xsX3R3ZWV0c191bnRva2VuDQpgYGANCg0KXA0KXA0KDQoqKjQuIFJlbW92ZSBzdG9wd29yZHMqKg0KDQpgYGB7cn0NCiNnZXQgcmlkIG9mIHN0b3B3b3JkcyAodGhlLCBhbmQsIGV0Yy4pDQp0cm9sbF90d2VldHNfY2xlYW5lZCA8LSB0cm9sbF90d2VldHNfdW50b2tlbiAlPiUNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpDQpgYGANCg0KVGFrZSBhIGxvb2sgYXQgdGhlIHRyb2xsX3R3ZWV0c19jbGVhbmVkIGRhdGFzZXQuICBBcmUgdGhlcmUgYW55IG90aGVyIHdvcmRzL2xldHRlcnMvbnVtYmVycyB0aGF0IHdlIHdhbnQgdG8gZWxpbWluYXRlIHRoYXQgd2VyZW4ndCB0YWtlbiBjYXJlIG9mIGJ5IHN0b3Bfd29yZHM/DQoNCmBgYHtyfQ0KI2dldCByaWQgb2YgaHR0cCwgaHR0cHMsIHQuY28sIHJ0LCBhbXAsIHNpbmdsZSBudW1iZXIgZGlnaXRzLCBhbmQgc2luZ3VsYXIgbGV0dGVycw0KdHJvbGxfdHdlZXRzX2NsZWFuZWQgPC0gdHJvbGxfdHdlZXRzX2NsZWFuZWQgJT4lDQogIGZpbHRlcih3b3JkICE9ICcxJywgd29yZCAhPSAnMicsIHdvcmQgIT0gJzMnLCB3b3JkICE9ICc0Jywgd29yZCAhPSAnNScsIHdvcmQgIT0gJzEwJywgd29yZCAhPSAncnQnLCB3b3JkICE9ICdhbXAnLCB3b3JkICE9ICdodHRwJywgISh3b3JkICVpbiUgbGV0dGVycyksIHdvcmQgIT0gJ2h0dHBzJywgd29yZCAhPSAndC5jbycpDQpgYGANCg0KXA0KXA0KDQoqKjUuIExvb2sgYXQgYSBzdWJzZXQgb2YgdGhlIHR3ZWV0cyB0byBzZWUgaG93IG9mdGVuIHRoZSB0b3Agd29yZHMgYXBwZWFyLioqDQoNCmBgYHtyfQ0KdHJvbGxfdHdlZXRzX3NtYWxsIDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVkICU+JQ0KICBjb3VudCh3b3JkKSAlPiUNCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gbiwgbiA9IDUwKQ0KZ2dwbG90KHRyb2xsX3R3ZWV0c19zbWFsbCwgYWVzKHkgPSBmY3RfcmVvcmRlcih3b3JkLG4pLCB4ID0gbikpICsNCiAgZ2VvbV9jb2woKQ0KYGBgDQoNClwNClwNCg0KKio2LiBTZW50aW1lbnQgQW5hbHlzaXMqKg0KDQogIGEuIEdldCB0aGUgc2VudGltZW50cyB1c2luZyB0aGUgImJpbmciIHBhcmFtZXRlciAod2hpY2ggY2xhc3NpZmllcyB3b3JkcyBpbnRvICJwb3NpdGl2ZSIgb3IgIm5lZ2F0aXZlIikNCiAgYi4gUmVwb3J0IGhvdyBtYW55IHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyB0aGVyZSBhcmUgaW4gdGhlIGRhdGFzZXQuICBBcmUgdGhlcmUgbW9yZSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSB3b3JkcywgYW5kIHdoeSBkbyB5b3UgdGhpbmsgdGhpcyBtaWdodCBiZT8NCmBgYHtyfQ0KIyBsb29rIGF0IHNlbnRpbWVudA0KZ2V0X3NlbnRpbWVudHMoImJpbmciKQ0KIyBhc3NpZ24gYSBzZW50aW1lbnQgdG8gZWFjaCB3b3JkIHRoYXQgaGFzIG9uZSBhc3NvY2lhdGVkDQp0cm9sbF90d2VldHNfc2VudGltZW50IDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVkICU+JQ0KICBpbm5lcl9qb2luKHNlbnRpbWVudHMpDQojIGNvdW50IHRoZSBzZW50aW1lbnRzDQp0cm9sbF90d2VldHNfc2VudGltZW50ICU+JSANCiAgY291bnQoc2VudGltZW50KQ0KYGBgDQoNClwNClwNCg0KKio3LiBVc2luZyB0aGUgdHJvbGxfdHdlZXRzX3NtYWxsIGRhdGFzZXQsIG1ha2UgYSB3b3JkY2xvdWQ6KioNCg0KICBhLiBUaGF0IGlzIHNpemVkIGJ5IHRoZSBudW1iZXIgb2YgdGltZXMgdGhhdCBhIHdvcmQgYXBwZWFycyBpbiB0aGUgdHdlZXRzDQogIGIuIFRoYXQgaXMgY29sb3JlZCBieSBzZW50aW1lbnQgKHBvc2l0aXZlIG9yIG5lZ2F0aXZlKQ0KDQpgYGB7cn0NCiMgbWFrZSBhIHdvcmRjbG91ZCB3aGVyZSB0aGUgc2l6ZSBvZiB0aGUgd29yZCBpcyBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoZSB3b3JkIGFwcGVhcnMgYWNyb3NzIHRoZSB0d2VldHMNCnRyb2xsX3R3ZWV0c19zbWFsbCAlPiUNCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMTAwKSkNCiMgbWFrZSBhIHdvcmRjbG91ZCBjb2xvcmVkIGJ5IHNlbnRpbWVudA0KdHJvbGxfdHdlZXRzX3NlbnRpbWVudCAlPiUNCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgZmlsbCA9IDApICU+JQ0KICBjb21wYXJpc29uLmNsb3VkKGNvbG9ycyA9IGMoInJlZCIsICJwdXJwbGUiKSwNCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSA1MCkNCmBgYA0KDQpBcmUgdGhlcmUgYW55IHdvcmRzIHdob3NlIGNhdGVnb3JpemF0aW9uIGFzICJwb3NpdGl2ZSIgb3IgIm5lZ2F0aXZlIiBzdXJwcmlzZWQgeW91Pw0KdHJ1bXAgYXMgcG9zaXRpdmUgd2FzIHN1cHJpc2luZyBidXQgdGhpcyB3YXMgcHJvYmFibHkgZHVlIHRvIHRoZSB2ZXJiICd0cnVtcCcNCg0KIyMgUHJvamVjdHMNCg0KUmVhZCB0aGUgcHJvamVjdCBkZXNjcmlwdGlvbiBvbiB0aGUgbW9vZGxlIHBhZ2UuIFRhbGsgdG8geW91ciBncm91cCBtZW1iZXJzIGFib3V0IHBvdGVudGlhbCB0b3BpY3MuIA0KDQoqKlRhc2s6KioNCg0KV3JpdGUgYSBzaG9ydCBwYXJhZ3JhcGggYWJvdXQgaWRlYXMgeW91IGhhdmUuIElmIHlvdSBhbHJlYWR5IGhhdmUgc29tZSBkYXRhIHNvdXJjZXMgaW4gbWluZCwgeW91IGNhbiBsaW5rIHRvIHRob3NlLCBidXQgSSdtIG1vcmUgY29uY2VybmVkIHdpdGggeW91IGhhdmluZyBhIHRvcGljIHRoYXQgeW91J3JlIGludGVyZXN0ZWQgaW4gaW52ZXN0aWdhdGluZyByaWdodCBub3cuIA0KDQpEaXNlYXNlIHJpc2sgY2xhc3NpZmljYXRpb24gc291bmRzIHZlcnkgaW50ZXJlc3RpbmcgdG8gbWUuIA0KRGF0YXNldDogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9yb25pdGYvaGVhcnQtZGlzZWFzZS11Y2kNCg0KIyMgIlVuZG9pbmciIGJpYXMNCg0KKipUYXNrOioqDQoNClJlYWQgdGhpcyB0d2VldCBbdGhyZWFkXShodHRwczovL3RocmVhZHJlYWRlcmFwcC5jb20vdGhyZWFkLzEzNzU5NTcyODQwNjEzNzY1MTYuaHRtbCkgYnkgW0RlYiBSYWppXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EZWJvcmFoX1JhamkpIHdobyB5b3UgbWF5IHJlbWVtYmVyIGZyb20gdGhlICpDb2RlZCBCaWFzKiBmaWxtLiBXcml0ZSBhIHNob3J0IHBhcmFncmFwaCB0aGF0IGRpc2N1c3NlcyBhdCBsZWFzdCBvbmUgb2YgdGhlIG1pc2NvbmNlcHRpb25zLg0KDQpUaGUgcG9pbnQgbWFkZSBhYm91dCBob3cgcmFjZSBhbmQgZ2VuZGVyIGFyZSB0aGUgbGVhc3Qgb2J2aW91cyBiaWFzZXMgdG8gZGV0ZWN0IHdhcyBpbnRlcmVzdGluZy4gU2luY2UgdGhpcyBpcyBsZWdhbGx5IHByb3RlY3RlZCBpbmZvcm1hdGlvbiwgeW91IGNhbid0IGhhdmUgdGhpcyBkYXRhIGFuZCBpdCdzIHByb2JhYmx5IGhhcmQgdG8gaWRlbnRpZnkgcHJveGllcyBmb3IgdGhlc2UgaW4gZGlmZmVyZW50IGRhdGFzZXRzIGluIGRpZmZlcmVudCBjb250ZXh0cy4NClRoZSBwYXBlciBhYm91dCBob3cgcmFjaWFsIGNhdGVnb3JpZXMgd2FzIGFsc28gaW50ZXJlc3RpbmcuIEl0IHN0YXRlcyB0aGF0IHRoZXNlIGNhdGVnb3JpZXMgYXJlIGlsbCBkZWZpbmVkIGFuZCB0aGUgc2FtcGxlIG1pZ2h0IGJlIG1pc3ByZXByZXNlbnRlZCB3aGVuIGNvbXBhcmlzb25zIGFyZSBtYWRlIGFjcm9zcyBkaWZmZXJlbnQgZGF0YXNldHMgdGhhdCBhcmUgdXNpbmcgdGhlc2UgcG9vcmx5IGRlZmluZWQgY2F0ZWdvcmllcy4gIA==